/*
 * BusStation.cpp
 *
 *  Created on: Jul 18, 2013
 *      Author: m
 */

#include "BusStation.h"

using communication::ConcurrentMessageQueue;

namespace application
{

BusStation::BusStation( busStationId busStationId, busStationType type ) :
    m_type( type ),
    m_busStationId( busStationId ),
    m_attachedBusId( "" ),
    m_newPassengerNotificationQueue( NULL ),
    m_inputChannel(
            new ConcurrentMessageQueue<BusStationRequest>(
                    BUS_STATION_REQUEST_BASENAME + m_busStationId ) ),
    m_passengerQueue(
            new ConcurrentMessageQueue<PassengerToLoad>(
                    BUS_STATION_PASSENGER_BASENAME + m_busStationId ) )
{
}

BusStation::~BusStation()
{
    delete m_inputChannel;

    if ( !m_attachedBusId.empty() )
    {
        delete m_newPassengerNotificationQueue;
    }
}

void BusStation::attachBus( busId busId )
{
    if ( busId.empty() )
    {
        throw Exception("Invalid bus to attach to terminal.");
    }

    m_attachedBusId = busId;

    m_newPassengerNotificationQueue = new ConcurrentMessageQueue<NewPassengerNotification>( PASSENGER_NOTIFICATION_BASENAME + busId );
}

void BusStation::enqueuePassenger()
{
    BusStationRequest request;
    BusStationReply reply;

    if ( m_attachedBusId.empty() )
    {
        throw Exception("No bus attached to this terminal.");
    }

    std::clog << "Waiting passengers from: " << BUS_STATION_REQUEST_BASENAME + m_busStationId << std::endl;

    request = m_inputChannel->get();

    Visitor visitor( request.m_visitor );

    std::clog << "Passenger arrive: " << visitor.getId() << std::endl;

    // Si el ticket es válido acepto al pasajero, sino lo rechazo
    if ( visitor.getTicket().isValidFor( waitEntranceBus ) )
    {
        /*
         * Aceptar al pasajero implica:
         *  1. Encolar al pasajero en la cola de esta sala
         *  2. Notificar al bus asociado a la busStation que hay un nuevo pasajero esperandolo
         *  3. Avisarle al pasajero que fue encolado
         */

        /*
         * 1. Encolo al pasajero en la cola de pasajeros de esta sala:
         *  1. Para ello primero debo "crear" un canal para que se comuniquen el bus con el pasajero
         *  2. Luego armo el mensaje de pasajero de bus encolado y lo pongo en la cola de salida de
         *      esta sala de bus
         */

        PassengerToLoad enqueuedPassenger;
        enqueuedPassenger.m_replyTo = this->getLoadRequestQueue( visitor );
        enqueuedPassenger.m_visitor = visitor.serialize();

        m_passengerQueue->put( enqueuedPassenger );

        /*
         * 2. Notifico al bus que hay un nuevo pasajero esperandolo
         */

        NewPassengerNotification notification;

        m_newPassengerNotificationQueue->put( notification );


        /*
         * 3. Le aviso al pasajero que fue encolado, armando el reply correspondiente:
         *  1. Resultado del pedido: SUCCESS
         *  2. Nuevo ticket inhabilitandolo para volver a repetir esta acción
         *  3. Nombre del canal por el que el bus se va a comunicar
         */

        reply.m_result = SUCCESS;

        this->giveBusPass( visitor );

        reply.m_ticket = visitor.getTicket().serialize();

        this->replyPassengerRequest( request, reply );

        std::clog << "Passenger ACCEPTED." << std::endl;
    }
    else
    {
        reply.m_result = INVALID_TICKET;

        this->replyPassengerRequest( request, reply );

        std::clog << "Passenger REJECTED." << std::endl;
    }
}

std::string BusStation::getLoadRequestQueue( Visitor &visitor )
{
    std::string queueName( m_attachedBusId + ".load." + visitor.getId() + ".from." + m_busStationId );

    return queueName;
}

void BusStation::replyPassengerRequest( BusStationRequest request, BusStationReply &reply )
{
    ConcurrentMessageQueue<BusStationReply> channel( std::string( request.m_replyTo.m_data ) );

    channel.put( reply );
}

void BusStation::giveBusPass( Visitor &visitor )
{
    Ticket &ticket = visitor.getTicket();

    BusPass pass;

    pass.m_busName = m_attachedBusId;
    pass.m_loadingAddress = this->getLoadRequestQueue( visitor );

    if ( m_type == ENTRANCE_BUS_STATION )
    {
        ticket.m_entranceBusPass = pass;
    }
    else
    {
        ticket.m_exitBusPass = pass;
    }
}

} /* namespace application */
